package com.zhou.framework.redis.util;

import cn.hutool.core.util.RandomUtil;
import com.zhou.framework.constant.AppEnum;
import com.zhou.framework.exception.LockException;
import com.zhou.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import java.time.Duration;
import java.util.concurrent.TimeUnit;


/**
 * @author lang.zhou
 * @date 2022/4/7 11:42
 */
@Slf4j
@Configuration
public class RedisLock {
    public RedisTemplate<String, Object> redisTemplate;

    public RedisLock(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }


    /**
     *  尝试加锁，返回加锁成功或者失败
     * @param time 秒
     **/
    public boolean tryLock(String key,Long time){
        return tryLock(key, 0, time);
    }

    public boolean tryLock(String key,Object value,Long time){
        if(time == null || time <= 0){
            time = AppEnum.DEFAULT_LOCK_TIME;
        }
        Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(time));
        return ObjectUtil.saveToBoolean(b,false);
    }
    /**
     *  尝试加锁，返回加锁成功或者失败
     * @param time 秒
     **/
    public void tryLock(String key,Long time, String msg){
        boolean b = this.tryLock(key,"1",time);
        if(!b){
            throw new LockException(msg);
        }
    }
    /**
     *  释放锁（拿到锁之后才能调用释放锁）
     **/
    public boolean unLock(String key){
        Boolean b = redisTemplate.delete(key);
        return ObjectUtil.saveToBoolean(b,false);
    }

    public boolean lockAndRun(String key, Long time, Runnable run){
        boolean b = tryLock(key, time);
        if(b){
            try{
                run.run();
            }finally {
                this.unLock(key);
            }
        }
        return b;
    }


    public boolean lockAndRun(String key, Long time, int retryCnt, Runnable run){
        do {
            boolean b = lockAndRun(key, time, run);
            if(b){
                log.info("try lock successful, key is {}.", key);
                return true;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(500, 1000));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.info("try lock interrupted, key is {}.", key);
            }
        }while ((retryCnt <= 0 || --retryCnt > 0) && !Thread.currentThread().isInterrupted());

        return false;
    }

}
